# hex.rb # usage: ruby hex.rb yourUsername yourPassword opponentUsername # Programming by Eric Rollins # # Copyright (c) 2002 Eric Rollins # www.acm.org/~rollins # hex comes with Absolutely No Warranty. # This is free software, and you are welcome to # redistribute it under certain conditions # (for details see:GNU General Public License, # http://www.gnu.org/copyleft/gpl.html) # version 1.3 - 11x11 board require 'tk' # uses ruby jabber library require 'rjab/connection' # constants SIDE_NONE = 0 SIDE_RED = 1 SIDE_BLUE = 2 # edit here for board size ROWS = 11 COLS = 11 #============================================================ def oppositeSide(side) if side == SIDE_RED return SIDE_BLUE elsif side == SIDE_BLUE return SIDE_RED end return SIDE_NONE end #============================================================ # View class Canvas SCALE = 20 XOFF = 20 YOFF = 20 WIDTH = (2.8*SCALE*COLS).to_i HEIGHT = (1.7*SCALE*ROWS).to_i def initialize(parent) @canvas = TkCanvas.new(parent) { width WIDTH; height HEIGHT } @canvas.pack @canvas.bind("ButtonRelease-1", proc{ |x, y| do_release(x,y)}, "%x %y") @pieces = Array.new drawPiece(3,-2,SIDE_BLUE,FALSE) drawPiece(3,(COLS+1),SIDE_BLUE,FALSE) end #------------------------------------------------------------------------------------------------------------------------ def clear for p in @pieces @canvas.delete(p) end end #------------------------------------------------------------------------------------------------------------------------ def drawHex(row,col) x = XOFF + row*0.86*SCALE+col*1.72*SCALE y = YOFF + row*1.5*SCALE TkcLine.new(@canvas, x, 0.5*SCALE + y, 0.86*SCALE + x, y, 1.72*SCALE + x, 0.5*SCALE + y, 1.72*SCALE + x, 1.5*SCALE + y, 0.86*SCALE + x, 2.0*SCALE + y, x, 1.5*SCALE + y, x, 0.5*SCALE + y, 0.86*SCALE + x, y) end #------------------------------------------------------------------------------------------------------------------------ def drawPiece(row,col,side,inPieces = TRUE) x = XOFF + row*0.86*SCALE+col*1.72*SCALE y = YOFF + row*1.5*SCALE if side != SIDE_NONE circle = TkcOval.new(@canvas, x+ SCALE/3.5, y + SCALE/3.5, x + 2.0*SCALE - SCALE/2.3, y + 2.0*SCALE - SCALE/2.3) if inPieces @pieces << circle end if side == SIDE_RED circle.fill('red') elsif side == SIDE_BLUE circle.fill('blue') end end end #------------------------------------------------------------------------------------------------------------------------ def do_release(x,y) if $app.getSide == SIDE_NONE || $app.getWaiting return end row = (y - YOFF) / (1.5*SCALE) if row < 0 || row > (ROWS) return end row = row.to_i col = (x - XOFF - row*0.86*SCALE) / (1.72*SCALE) if col < 0 || col > (COLS) return end col = col.to_i if $app.getGame.getBoard.getCell(row,col) != SIDE_NONE return end $app.getGame.setCell(row,col,$app.getSide) $app.send_turn(row,col) end end #============================================================ # Model class Board def initialize @board = Array.new for row in 0..ROWS-1 @board[row] = Array.new end clear end #----------------------------------------------------------------------------------------------------------------------- def clear for row in 0..ROWS-1 for col in 0..COLS-1 @board[row][col] = SIDE_NONE end end end #----------------------------------------------------------------------------------------------------------------------- def getCell(row,col) return @board[row][col] end #------------------------------------------------------------------------------------------------------------------------ def setCell(row,col,val) @board[row][col] = val end end #============================================================ # controller for Canvas (View) + Board (Model) class Game def initialize(parent) @canvas = Canvas.new(parent) @board = Board.new end #------------------------------------------------------------------------------------------------------------------------ def drawAll for row in 0..(ROWS-1) for col in 0..(COLS-1) @canvas.drawHex(row,col) @canvas.drawPiece(row,col,@board.getCell(row,col)) end end end #------------------------------------------------------------------------------------------------------------------------ def setCell(row,col,side) cur = @board.getCell(row,col) if cur == SIDE_RED || cur == SIDE_BLUE puts "ERROR: cell already set!!" exit end @board.setCell(row,col,side) @canvas.drawPiece(row,col,side) end #------------------------------------------------------------------------------------------------------------------------ def newGame @canvas.clear @board.clear end #------------------------------------------------------------------------------------------------------------------------ def getBoard return @board end end #============================================================ class Connection def initialize(username,password,opponent) @username = username @password = password @opponent = "%s@jabber.org" % opponent @conn = Jabber::Connection.new('jabber.org',nil,nil,'local') end #------------------------------------------------------------------------------------------------------------------------ def connect @conn.connect @conn.register_handler('message') do | node | puts node if node.root.elements['error'] != nil puts 'ERROR: network error' exit else @readData = node.root.elements['body'].text end end @conn.auth(@username,@password,'hex') @conn.send('') end #------------------------------------------------------------------------------------------------------------------------ def disconnect @conn.disconnect end #------------------------------------------------------------------------------------------------------------------------ def send(message) m = Jabber::Node.new_message(@opponent, message) @conn.send(m) end #------------------------------------------------------------------------------------------------------------------------ def receive @readData = nil while @readData == nil @conn.process(1) end return @readData end end #============================================================ class App WIDTH = 400 def recv_turn setWaiting(TRUE) Thread.new { puts "about to blocking recv" data = @conn.receive puts "recvd: " + data setStatusText('opponent move ' + data + ' ; select move') splitData = data.split(',') row = splitData[0].to_i col = splitData[1].to_i if (row < 0) || (row > (ROWS-1)) || (col < 0) || (col > (COLS-1)) puts "ERROR: recd row or col out of bounds" exit end @game.setCell(row,col,oppositeSide($app.getSide)) setWaiting(FALSE) } end #------------------------------------------------------------------------------------------------------------------------ def send_turn(row,col) msg = "%d,%d" % [row,col] puts "about to send: " + msg @conn.send(msg) setStatusText('Waiting for opponents move...') recv_turn end #------------------------------------------------------------------------------------------------------------------------ def start_blue if $app.getWaiting return end setSide(SIDE_BLUE) @game.newGame @rNewBtn.state('disabled') @bNewBtn.state('disabled') setSideMsg('Side = Blue') setStatusText('Waiting for Red opponents first move...') recv_turn end #------------------------------------------------------------------------------------------------------------------------ def start_red if $app.getWaiting return end setSide(SIDE_RED) @game.newGame @rNewBtn.state('disabled') @bNewBtn.state('disabled') setSideMsg('Side = Red') setStatusText('Select move') end #------------------------------------------------------------------------------------------------------------------------ def setStatusText(msg) @status.text(msg) @status.width(WIDTH) @status.pack end #------------------------------------------------------------------------------------------------------------------------ def getSide return @side end def setSide(side) @side = side end #------------------------------------------------------------------------------------------------------------------------ def setSideMsg(msg) @sideMsg.text(msg) @sideMsg.width(WIDTH) @sideMsg.pack end #------------------------------------------------------------------------------------------------------------------------ def getWaiting return @waiting end def setWaiting(waiting) @waiting = waiting end #------------------------------------------------------------------------------------------------------------------------ def getGame return @game end #------------------------------------------------------------------------------------------------------------------------ def initialize @side = SIDE_NONE @waiting = FALSE end #------------------------------------------------------------------------------------------------------------------------ def run ph = { 'padx' => 10, 'pady' => 10 } # tk formatting if ARGV[0] != nil titleMsg = "Hex - " + ARGV[0] else titleMsg = "Hex - " end root = TkRoot.new { title titleMsg } top = TkFrame.new(root) @game = Game.new(top) @game.drawAll @sideMsg = TkMessage.new(top) { text 'Side = Unset' ; width WIDTH; pack ph } @rNewBtn = TkButton.new(top) { text 'Start New Game As Red (moves first)'; pack ph; command { $app.start_red}} @bNewBtn = TkButton.new(top) { text 'Start New Game As Blue (moves second)'; pack ph; command { $app.start_blue}} @status = TkMessage.new(top) { text 'Press '; width WIDTH; pack ph } top.pack if ARGV.length != 3 setStatusText('ERROR: must specify on command line') @rNewBtn.state('disabled') @bNewBtn.state('disabled') else @conn = Connection.new(ARGV[0],ARGV[1],ARGV[2]) @conn.connect end begin Tk.mainloop ensure if @conn != nil @conn.disconnect puts 'disconnected' end end end end #============================================================ $app = App.new $app.run